package functions

import (
	"errors"
	"fmt"

	"github.com/project-flogo/core/data/coerce"

	"github.com/project-flogo/core/data"
	"github.com/project-flogo/core/data/expression/function"
	"github.com/project-flogo/core/data/mapper"
	"github.com/project-flogo/core/data/resolve"
)

func init() {
	err := function.Register(&fnMapKV{})
	if err != nil {
		panic(err)
	}
}

type fnMapKV struct{}

func (fnMapKV) Name() string {
	return "mapKV"
}

func (fnMapKV) Sig() (paramsTypes []data.Type, isVariadic bool) {
	return []data.Type{data.TypeObject, data.TypeArray}, false
}

type mapperEntry struct {
	Filter    string
	Transform string
}

type mapperExpr struct {
	Filter    mapper.Mapper
	Transform mapper.Mapper
}

func (fnMapKV) Eval(params ...interface{}) (interface{}, error) {
	if len(params) < 2 {
		return nil, fmt.Errorf("wrong number of params, expect %d, got %d", 2, len(params))
	}
	mappers, err := coerce.ToArray(params[1])
	if err != nil {
		return nil, errors.New("expect a array of mappers")
	}
	mapperExprs := []*mapperExpr{}
	for idx, mp := range mappers {
		mpObj, err := coerce.ToObject(mp)
		if err != nil {
			return nil, fmt.Errorf("invalid mapper, not object: %d", idx)
		}
		me, err := toMapperEntry(mpObj)
		if err != nil {
			return nil, fmt.Errorf("invalid mapper: %d", idx)
		}
		mExpr, err := toMapperExpr(me)
		if err != nil {
			return nil, err
		}
		mapperExprs = append(mapperExprs, mExpr)
	}

	dataScope, err := coerce.ToObject(params[0])
	if err != nil {
		return nil, errors.New("expect params 1 of type object")
	}
	scope := data.NewSimpleScope(dataScope, nil)
	for _, me := range mapperExprs {
		if applyFilter(me.Filter, scope) {
			value, err := applyTransform(me.Transform, scope)
			return value, err
		}
	}
	return nil, nil
}

func toMapperEntry(m map[string]interface{}) (*mapperEntry, error) {
	filter, err := coerce.ToString(m["filter"])
	if err != nil {
		return nil, err
	}
	transform, err := coerce.ToString(m["transform"])
	if err != nil {
		return nil, err
	}
	me := mapperEntry{
		Filter:    filter,
		Transform: transform,
	}
	return &me, nil
}

const (
	FilterKey    = "FILTER"
	TransformKey = "TRANSFORM"
)

func toMapperExpr(m *mapperEntry) (*mapperExpr, error) {
	if m == nil {
		return nil, errors.New("nil parameter")
	}
	f := mapper.NewFactory(resolve.GetBasicResolver())
	filterMapping := map[string]interface{}{
		FilterKey: m.Filter,
	}
	filterMapper, err := f.NewMapper(filterMapping)
	if err != nil {
		return nil, err
	}
	transformMapping := map[string]interface{}{
		TransformKey: m.Transform,
	}
	transformMapper, err := f.NewMapper(transformMapping)
	if err != nil {
		return nil, err
	}
	me := mapperExpr{
		Filter:    filterMapper,
		Transform: transformMapper,
	}
	return &me, nil
}

func applyFilter(filter mapper.Mapper, scope data.Scope) bool {
	if results, err := filter.Apply(scope); err == nil {
		if r, err := coerce.ToBool(results[FilterKey]); err == nil {
			return r
		}
		return false
	}
	return false
}

func applyTransform(transform mapper.Mapper, scope data.Scope) (interface{}, error) {
	if results, err := transform.Apply(scope); err == nil {
		return results[TransformKey], nil
	} else {
		return nil, err
	}
}
